<?php namespace lib;

use \Proc;
use \Hprose\Future ;
use \Workerman\Worker;
use \Workerman\Events\EventInterface;
use function \Hprose\Future\co;

class BaseActor
{

 
    public $mailbox = null;
    public $receive = null;
    public $trapexit = false;
    public $relation = [];
    public static $main = false;
    public static $gens=[]; //[ 本地标识（hash) =>''  ]
    public static $local_id=0;
 
    public function __construct()
    {
        $this->mailbox = new ArrayQueue();
        $this->receive = new CircleQueue();
        $this->coid = self::$local_id++  ;//\spl_object_hash($this);
    }
    public function __destruct()
    {
        //echo "}\n";
        try {
            foreach ($this->relation as $c => $TRUE) {
                static::exit($c, $this->coid);
            }
        } catch (\Throwable $e) {
            echo $e;
        }
        if(self::$main && !count(self::$gens)){
            exit;
        }
    }

    ///////////////// static 异步函数 ///////////////////
    
    public static function delay($time)
    {
        $defer = new Future();
        $event = Worker::$globalEvent;
        $gen = \Proc::getContext();
        $timer = $event->add($time, EventInterface::EV_TIMER_ONCE, function () use ($defer, $gen, &$timer) {
            unset($gen->onCancel[$timer]);
            $defer->resolve();
        });
        $gen->onCancel[$timer]=function () use ($timer) {
            $event = Worker::$globalEvent;
            $event->del($timer, EventInterface::EV_TIMER_ONCE);
        };
        return $defer;
    }
    
    ///////////////// static 同步函数 ///////////////////

    public static function spawn_opt($fn, $args = [], $opt = [])
    {
        $opt += [ 'class' => static::class ];
        //return co(function () use ($fn, $args, $opt) {
        /// 不要在这里断点调试会让actor会释放不了的（__destruct） ///
        ///↓↓↓↓↓↓↓↓ do not debug break in there ↓↓↓↓↓↓↓↓↓////////
        switch (true) {
            case is_string($fn) && substr($fn, 0, 38)
                =='C:32:"Opis\Closure\SerializableClosure':
                $fn = unserialize($fn);
                $fn = $fn->getClosure();  
            case $fn instanceof \Closure:
                $actor = new $opt['class'] ;
                $fn = $fn->bindTo($actor);
                break;
            case \is_array($fn):
                list($class,$method) = $fn;
                $fn = [$actor=new $class,$method];
                break;
            default:
                $actor = new $opt['class'] ;
                $fn = \Closure::fromCallable($fn)->bindTo($actor);
                break;
        }
        co(function () use ($fn, $args, $actor, $opt) {
            if(!static::$main && !empty($opt['main'])){
                static::$main = true; 
            }  
            $coid = $actor->coid;
            self::$gens[$coid] = \Proc::getContext();//声明后才能返回coid
            self::$gens[$coid]->onCancel = [];
            self::$gens[$coid]->actor = $actor;
            try {
                //////////// 未知bug //////////// 
                yield \Proc::yield(); 
                //////////// 未知bug ////////////
                //异步链接
                if (isset($opt['link'])) {
                    yield $actor->link($opt['link']);
                }
                if (method_exists($actor,'before')) {
                    ($result = $actor->before())
                        instanceof \Generator ? yield from $result : null ;
                }
                $result = \call_user_func($fn, $args);//$fn(...$args);
                if ($result instanceof \Generator) {
                    $return = yield from $result;
                }
                if (method_exists($actor,'after')) {
                    ($result = $actor->after($result)) 
                        instanceof \Generator ? yield from $result : null ;
                }
                //////////// 未知bug //////////// 
                yield \Proc::yield(); 
                //////////// 未知bug ////////////
            } catch (\Erro $e) {
                echo "THROW: ".$coid."       REASION: ". $e->getMessage()."\n";
                //忽略异常（手动抛出的错误）
            } catch (\Throwable $e) {
                echo $e; //throw $e
                //忽略co（未知抛出没有写接收器要直接打印）
            }
            foreach (self::$gens[$coid]->onCancel as $fn) {
                $fn();
            }
            unset(self::$gens[$coid]->onCancel);//关键 删除循环引用
            unset(self::$gens[$coid]->actor->mailbox);//关键 删除循环引用
            unset(self::$gens[$coid]->actor->receive);//actor:>receive:>promise:>gen:>actor
            unset(self::$gens[$coid]->actor);
            unset(self::$gens[$coid]);
            //echo "{E$coid";
        });
        ///↑↑↑↑↑↑↑↑↑↑ do not debug break in there ↑↑↑↑↑↑↑↑↑↑↑↑↑///
        /// 不要在这里断点调试会让actor会释放不了的（__destruct） ///
        return $actor->coid;
        //});
    }

    public static function spawn($fn, $args = [], $opt = [])
    {
        return static::spawn_opt($fn, $args, $opt);
    }
    
    public static function spawn_link($fn, $args = [])
    {
        return static::spawn($fn, $args,$args+=['link'=>static::self()]);
    }

    public static function send($to, $data)
    {
        $actor = self::$gens[$to]->actor;
        $i = $actor->mailbox->push($data);
        foreach ($actor->receive as $k => $defer) {
            if ($actor->notify($k, $defer, $i, $data )) {
                break;
            }
        }
    }
    
    public static function exit($coid, $reason)
    { 
        if (empty(self::$gens[$coid]->actor)) {
            return false;
        }
        if (self::$gens[$coid]->actor->trapexit) { 
            unset(self::$gens[$coid]->actor->relation[$reason]);
            self::send($coid, ['EXIT',$reason]); 
        } else { 
            self::$gens[$coid] ->throw(new \Error($reason));
            ///↑↑↑↑↑↑↑↑↑↑ do not debug break in there ↑↑↑↑↑↑↑↑↑↑↑↑↑///
            /// 不要在这里断点调试会让actor会释放不了的（__destruct） ///
        } 
    }

    public static function self() : string
    {
        $stack_list = debug_backtrace();
        for ($i=0; $i < count($stack_list); $i++) {
            $stack = $stack_list[$i];
            if (isset($stack['object']) && $stack['object'] instanceof self) {
                return $stack['object']->coid;
            }
        }
    }

    /**
     * 链接
     */
    public static function ref($dead, $notify):bool
    {
        if (isset( self::$gens[$dead]->actor )) {
            return self::$gens[$dead]->actor->relation[$notify]='true';
        } else {
            return false;
        }
    }
    /**
     * 断开
     */
    public static function out($dead, $notify):bool
    {
        if (isset( self::$gens[$dead]->actor )) {
            unset(self::$gens[$dead]->actor->relation[$notify]);
            return true;
        } else {
            return false;
        }
    }


    ///////////////// actor 同步函数 ///////////////////

    public function trapexit($bool)
    {
        $this->trapexit=$bool;
    }

    public function notify($k, $defer, $i, $msg)
    {
        try {
            if (null!==($data = \call_user_func($defer->fn, $msg))) {
                $this->mailbox->splice($i);
                $this->receive->splice($k);
                $defer->resolve($data);
                return true;
            }
        } catch (\Throwable $e) {
            $defer->reject($e);
            return true;
        }
    }
  
    public function link($coid)
    {
        if (!self::ref($coid, $this->coid)) {
            throw new \Exception("Error Processing Request", 1);
        }
        self::ref($this->coid, $coid);
        //return 'ok';
    }
    public function unlink($coid)
    {
        self::out($coid, $this->coid);
        self::out($this->coid, $coid);
        //return 'ok';
    }

    public function monitor($coid)
    {
        if (!self::ref($this->coid, $coid)) {
            throw new \Exception("Error Processing Request", 1);
        }
        //return 'ok';
    }
    public function demonitor($coid)
    {
        self::out($this->coid, $coid);
        //return 'ok';
    }


    //////////////// actor 异步函数 /////////////////////

    public function receive($receive = null/*(data)=>{ data };*/)
    {
        $defer =// new Future;
        new class extends Future{  function __debugInfo()  { return ['Deferred'=>__FILE__]; } };
        $defer->fn = $receive??function ($data) {
            return $data;
            // if (!\is_array($data)) {
            //     return $data;
            // }
        };
        $k = $this->receive->push($defer);
        foreach ($this->mailbox as $i => $msg) {
            if ($this->notify( $k, $defer, $i, $msg )) {
                break;
            }
        }
        return $defer;
    }
}
